home *** CD-ROM | disk | FTP | other *** search
/ Clickx 47 / Clickx 47.iso / assets / software / Miro_Installer.exe / xulrunner / python / iconcache.py < prev    next >
Encoding:
Python Source  |  2008-01-10  |  11.3 KB  |  352 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. import item
  19. import os
  20. import threading
  21. import httpclient
  22. from fasttypes import LinkedList
  23. from eventloop import asIdle, addIdle, addTimeout
  24. from download_utils import nextFreeFilename, getFileURLPath
  25. from util import unicodify, call_command
  26. from platformutils import unicodeToFilename
  27. import config
  28. import prefs
  29. import time
  30. import views
  31. import random
  32. import imageresize
  33.  
  34. RUNNING_MAX = 3
  35.     
  36. def clearOrphans():
  37.     knownIcons = set()
  38.     for item in views.items:
  39.         if item.iconCache and item.iconCache.filename:
  40.             knownIcons.add(os.path.normcase(item.iconCache.filename))
  41.             for resized in item.iconCache.resized_filenames.values():
  42.                 knownIcons.add(os.path.normcase(resized))
  43.     for feed in views.feeds:
  44.         if feed.iconCache and feed.iconCache.filename:
  45.             knownIcons.add(os.path.normcase(feed.iconCache.filename))
  46.             for resized in feed.iconCache.resized_filenames.values():
  47.                 knownIcons.add(os.path.normcase(resized))
  48.     cachedir = config.get(prefs.ICON_CACHE_DIRECTORY)
  49.     if os.path.isdir(cachedir):
  50.         existingFiles = [os.path.normcase(os.path.join(cachedir, f)) 
  51.                 for f in os.listdir(cachedir)]
  52.         for filename in existingFiles:
  53.             if (os.path.exists(filename) and
  54.                 os.path.basename(filename)[0] != '.' and
  55.                 os.path.basename(filename) != 'extracted' and
  56.                 not filename in knownIcons):
  57.                 try:
  58.                     os.remove (filename)
  59.                 except OSError:
  60.                     pass
  61.  
  62. class IconCacheUpdater:
  63.     def __init__ (self):
  64.         self.idle = LinkedList()
  65.         self.vital = LinkedList()
  66.         self.runningCount = 0
  67.         self.inShutdown = False
  68.  
  69.     @asIdle
  70.     def requestUpdate (self, item, is_vital = False):
  71.         if is_vital:
  72.             item.dbItem.confirmDBThread()
  73.             if item.filename and os.access (item.filename, os.R_OK):
  74.                 is_vital = False
  75.         if self.runningCount < RUNNING_MAX:
  76.             addIdle (item.requestIcon, "Icon Request")
  77.             self.runningCount += 1
  78.         else:
  79.             if is_vital:
  80.                 self.vital.prepend(item)
  81.             else:
  82.                 self.idle.prepend(item)
  83.  
  84.     def updateFinished (self):
  85.         if self.inShutdown:
  86.             self.runningCount -= 1
  87.             return
  88.  
  89.         if len (self.vital) > 0:
  90.             item = self.vital.pop()
  91.         elif len (self.idle) > 0:
  92.             item = self.idle.pop()
  93.         else:
  94.             self.runningCount -= 1
  95.             return
  96.         
  97.         addIdle (item.requestIcon, "Icon Request")
  98.  
  99.     @asIdle
  100.     def clearVital (self):
  101.         self.vital = LinkedList()
  102.  
  103.     @asIdle
  104.     def shutdown (self):
  105.         self.inShutdown = True
  106.  
  107. iconCacheUpdater = IconCacheUpdater()
  108. class IconCache:
  109.     def __init__ (self, dbItem, is_vital = False):
  110.         self.etag = None
  111.         self.modified = None
  112.         self.filename = None
  113.         self.resized_filenames = {}
  114.         self.url = None
  115.  
  116.         self.updated = False
  117.         self.updating = False
  118.         self.needsUpdate = False
  119.         self.dbItem = dbItem
  120.         self.removed = False
  121.  
  122.         self.requestUpdate (is_vital=is_vital)
  123.  
  124.     def iconChanged (self, needsSave=True):
  125.         try:
  126.             self.dbItem.iconChanged(needsSave=needsSave)
  127.         except:
  128.             self.dbItem.signalChange(needsSave=needsSave)
  129.  
  130.     def remove (self):
  131.         self.removed = True
  132.         self._removeFile(self.filename)
  133.         imageresize.removeResizedFiles(self.resized_filenames)
  134.  
  135.     def reset (self):
  136.         self._removeFile(self.filename)
  137.         imageresize.removeResizedFiles(self.resized_filenames)
  138.         self.filename = None
  139.         self.resized_filenamed = {}
  140.         self.url = None
  141.         self.etag = None
  142.         self.modified = None
  143.         self.removed = False
  144.         self.updated = False
  145.         self.updating = False
  146.         self.needsUpdate = False
  147.         self.iconChanged()
  148.  
  149.     def _removeFile(self, filename):
  150.         try:
  151.             os.remove (filename)
  152.         except:
  153.             pass
  154.  
  155.     def errorCallback(self, url, error = None):
  156.         self.dbItem.confirmDBThread()
  157.  
  158.         if self.removed:
  159.             iconCacheUpdater.updateFinished()
  160.             return
  161.  
  162.         # Don't clear the cache on an error.
  163.         if self.url != url:
  164.             self.url = url
  165.             self.etag = None
  166.             self.modified = None
  167.             self.iconChanged()
  168.         self.updating = False
  169.         if self.needsUpdate:
  170.             self.needsUpdate = False
  171.             self.requestUpdate()
  172.         elif error is not None:
  173.             addTimeout(3600,self.requestUpdate, "Thumbnail request for %s" % url)
  174.         else:
  175.             self.updated = True
  176.         iconCacheUpdater.updateFinished ()
  177.  
  178.     def updateIconCache (self, url, info):
  179.         self.dbItem.confirmDBThread()
  180.  
  181.         if self.removed:
  182.             iconCacheUpdater.updateFinished()
  183.             return
  184.  
  185.         needsSave = False
  186.         needsChange = False
  187.  
  188.         if info == None or (info['status'] != 304 and info['status'] != 200):
  189.             self.errorCallback(url)
  190.             return
  191.         try:
  192.             # Our cache is good.  Hooray!
  193.             if (info['status'] == 304):
  194.                 self.updated = True
  195.                 return
  196.  
  197.             needsChange = True
  198.  
  199.             # We have to update it, and if we can't write to the file, we
  200.             # should pick a new filename.
  201.             if (self.filename and not os.access (self.filename, os.R_OK | os.W_OK)):
  202.                 self.filename = None
  203.                 seedsSave = True
  204.  
  205.             cachedir = config.get(prefs.ICON_CACHE_DIRECTORY)
  206.             try:
  207.                 os.makedirs (cachedir)
  208.             except:
  209.                 pass
  210.  
  211.             try:
  212.                 # Write to a temp file.
  213.                 if (self.filename):
  214.                     tmp_filename = self.filename + ".part"
  215.                 else:
  216.                     tmp_filename = os.path.join(cachedir, info["filename"]) + ".part"
  217.  
  218.                 tmp_filename = nextFreeFilename (tmp_filename)
  219.                 output = file (tmp_filename, 'wb')
  220.                 output.write(info["body"])
  221.                 output.close()
  222.             except IOError:
  223.                 try:
  224.                     os.remove (tmp_filename)
  225.                 except:
  226.                     pass
  227.                 return
  228.  
  229.             if (self.filename == None):
  230.                 # Add a random unique id
  231.                 parts = unicodify(info["filename"]).split('.')
  232.                 uid = u"%08d" % (random.randint(0,99999999),)
  233.                 if len(parts) == 1:
  234.                     parts.append(uid)
  235.                 else:
  236.                     parts[-1:-1] = [uid]
  237.                 self.filename = u'.'.join(parts)
  238.                 self.filename = unicodeToFilename(self.filename, cachedir)
  239.                 self.filename = os.path.join(cachedir, self.filename)
  240.                 self.filename = nextFreeFilename (self.filename)
  241.                 needsSave = True
  242.             self._removeFile(self.filename)
  243.  
  244.             try:
  245.                 os.rename (tmp_filename, self.filename)
  246.             except:
  247.                 self.filename = None
  248.                 needsSave = True
  249.             else:
  250.                 self.resizeIcon()
  251.  
  252.  
  253.             if (info.has_key ("etag")):
  254.                 etag = unicodify(info["etag"])
  255.             else:
  256.                 etag = None
  257.  
  258.             if (info.has_key ("modified")):
  259.                 modified = unicodify(info["modified"])
  260.             else:
  261.                 modified = None
  262.  
  263.             if self.etag != etag:
  264.                 needsSave = True
  265.                 self.etag = etag
  266.             if self.modified != modified:
  267.                 needsSave = True
  268.                 self.modified = modified
  269.             if self.url != url:
  270.                 needsSave = True
  271.                 self.url = url
  272.             self.updated = True
  273.         finally:
  274.             if needsChange:
  275.                 self.iconChanged(needsSave=needsSave)
  276.             self.updating = False
  277.             if self.needsUpdate:
  278.                 self.needsUpdate = False
  279.                 self.requestUpdate()
  280.             iconCacheUpdater.updateFinished ()
  281.  
  282.     def requestIcon (self):
  283.         if self.removed:
  284.             iconCacheUpdater.updateFinished()
  285.             return
  286.  
  287.         self.dbItem.confirmDBThread()
  288.         if (self.updating):
  289.             self.needsUpdate = True
  290.             iconCacheUpdater.updateFinished ()
  291.             return
  292.         try:
  293.             url = self.dbItem.getThumbnailURL()
  294.         except:
  295.             url = self.url
  296.  
  297.         # Only verify each icon once per run unless the url changes
  298.         if (self.updated and url == self.url):
  299.             iconCacheUpdater.updateFinished ()
  300.             return
  301.  
  302.         self.updating = True
  303.  
  304.         # No need to extract the icon again if we already have it.
  305.         if url is None or url.startswith(u"/") or url.startswith(u"file://"):
  306.             self.errorCallback(url)
  307.             return
  308.  
  309.         # Last try, get the icon from HTTP.
  310.         if (url == self.url and self.filename and os.access (self.filename, os.R_OK)):
  311.             httpclient.grabURL (url, lambda info: self.updateIconCache(url, info), lambda error: self.errorCallback(url, error), etag=self.etag, modified=self.modified)
  312.         else:
  313.             httpclient.grabURL (url, lambda info: self.updateIconCache(url, info), lambda error: self.errorCallback(url, error))
  314.  
  315.     def requestUpdate (self, is_vital = False):
  316.         if hasattr (self, "updating") and hasattr (self, "dbItem"):
  317.             if self.removed:
  318.                 return
  319.  
  320.             iconCacheUpdater.requestUpdate (self, is_vital = is_vital)
  321.  
  322.     def onRestore(self):
  323.         self.removed = False
  324.         self.updated = False
  325.         self.updating = False
  326.         self.needsUpdate = False
  327.         self.requestUpdate ()
  328.  
  329.     def isValid(self):
  330.         self.dbItem.confirmDBThread()
  331.         return self.filename and os.path.exists(self.filename)
  332.  
  333.     def getFilename(self):
  334.         self.dbItem.confirmDBThread()
  335.         if self.url and self.url.startswith (u"file://"):
  336.             return getFileURLPath(self.url)
  337.         elif self.url and self.url.startswith (u"/"):
  338.             return self.url
  339.         else:
  340.             return self.filename
  341.  
  342.     def getResizedFilename(self, width, height):
  343.         try:
  344.             return imageresize.getImage(self.resized_filenames, width, height)
  345.         except KeyError:
  346.             return self.getFilename()
  347.  
  348.     def resizeIcon(self):
  349.         imageresize.removeResizedFiles(self.resized_filenames)
  350.         self.resized_filenames = imageresize.multiResizeImage(self.filename,
  351.                 self.dbItem.ICON_CACHE_SIZES)
  352.